#include "dib.h"
#include <assert.h>

DIB::DIB()
{
	_data = NULL;
	_dataSize = 0;
}

DIB::DIB( const DIB &dib )
{
	_data = new unsigned char[ dib._dataSize ];
	memcpy( _data, dib._data, dib._dataSize );
	_dataSize = dib._dataSize;
	_format = dib._format;
}

DIB::~DIB()
{
	if( _data )
	{
		delete[] _data;
	}
}

DIB& DIB::operator =( const DIB &dib )
{
	// Delete the old image.
	if( _data )
	{
		delete[] _data;
	}

	// Copy dib to this instance.
	_data = new unsigned char[ dib._dataSize ];
	memcpy( _data, dib._data, dib._dataSize );
	_dataSize = dib._dataSize;
	_format = dib._format;
	return *this;
}

Image DIB::getImage( RECT *rect )
{
	// This function returns an Image instance for this DIB. A rectangle
	// can be specified to create an Image for a part of the DIB. If rect
	// is NULL, an Image is created for the entire DIB.

	// An Image instance only handles 24 bit bottom-up images.
	assert( bitCount() == 24 );
	assert( bottomUp() );

	// The number of bytes per row is rounded up to the nearest quad word
	// (the result must thus be divisable by 4).
	unsigned int bytesPerRow = _format->bmiHeader.biWidth * 3;
	if( bytesPerRow % 4 ) bytesPerRow = ( bytesPerRow &= ~( (unsigned int) 3 ) ) + 4;

	if( rect )
	{
		// A negative width is not allowed.
		assert( rect->right >= rect->left );
		SIZE imageSize;
		
		if( rect->bottom > rect->top )
		{
			std::swap( rect->bottom, rect->top );
		}

		imageSize.cx = rect->right - rect->left;
		imageSize.cy = rect->top - rect->bottom;
		
		// Return the data pointer, which starts at the start of the returned
		// image itself (which is the bottom left corner of rect).
		unsigned char *data = _data + rect->bottom * bytesPerRow + rect->left * 3;
		return Image( bytesPerRow, data, imageSize );
	}
	else
	{
		// Return an image description for the whole dib.
		return Image( bytesPerRow, _data, size() );
	}
}

bool DIB::readBitmap( std::istream &strm )
{
	// Each Windows bitmap file (.bmp or .dib) starts with a BITMAPFILEHEADER.
	BITMAPFILEHEADER bitmapFileHeader;
	strm.read( (char*) &bitmapFileHeader, sizeof( BITMAPFILEHEADER ) );
	if( memcmp( &bitmapFileHeader.bfType, "BM", 2 ) != 0 )
	{
		return false; // Not a valid BMP file.
	}
	
	if( bitmapFileHeader.bfOffBits < sizeof( BITMAPFILEHEADER ) )
	{
		return false; // bfOffBits field is not set correctly.
	}
	
	// bfOffBits indicates the size (and type) of the total header. The rest
	// of the header will be read and stored into bitmapInfo.
	unsigned int bitmapInfoSize = bitmapFileHeader.bfOffBits - sizeof( BITMAPFILEHEADER );
	char *bitmapInfo = new char[ bitmapInfoSize ];
	strm.read( bitmapInfo, bitmapInfoSize );
	
	// Read the bitmap data (the pixel values).
	unsigned int dataSize = bitmapFileHeader.bfSize - bitmapFileHeader.bfOffBits;
	unsigned char *data = new unsigned char[ dataSize ];
	strm.read( (char*) data, dataSize );

	// Assign the bitmap to this instance. Delete the old bitmap data (if present).
	setFormat( (LPBITMAPINFO) bitmapInfo );
	setData( data, dataSize );

	return true;
}

bool DIB::saveBitmap( std::ostream &strm )
{
	// Create a file header for the bitmap (we did not store this one in memory).
	BITMAPFILEHEADER bitmapFileHeader;
	memcpy( &bitmapFileHeader.bfType, "BM", 2 );
	bitmapFileHeader.bfReserved1 = 0;
	bitmapFileHeader.bfReserved2 = 0;
	bitmapFileHeader.bfOffBits = sizeof( BITMAPFILEHEADER ) + sizeof( BITMAPINFOHEADER );
	bitmapFileHeader.bfSize = bitmapFileHeader.bfOffBits + _dataSize;

	strm.write( (const char*) &bitmapFileHeader, sizeof( BITMAPFILEHEADER ) );
	strm.write( (const char*) &_format->bmiHeader, sizeof( BITMAPINFOHEADER ) );
	strm.write( (const char*) _data, _dataSize );

	return true;
}

bool DIB::create()
{
	// Format must be specified.
	assert( &*_format );

	// Round the bytes per scanline up to the nearest quad word.
	unsigned int bytesPerScanLine = _format->bmiHeader.biWidth * ( _format->bmiHeader.biBitCount >> 3 );
	bytesPerScanLine += ( 4 - ( bytesPerScanLine % 4 ) ) % 4;

	// Create the data and set each byte to 0xFF (which results in a white image).
	unsigned char *newData = new unsigned char[ bytesPerScanLine * _format->bmiHeader.biHeight ];
	memset( newData, 0xFF, bytesPerScanLine * _format->bmiHeader.biHeight );

	// Delete any old data.
	if( _data )
	{
		delete[] _data;
	}
	_data = newData;
	_dataSize = bytesPerScanLine * _format->bmiHeader.biHeight;
	return true;
}

void DIB::setData( unsigned char *data, unsigned int dataSize )
{
	// Delete any old data.
	if( _data )
	{
		delete[] _data;
	}

	_data = data;
	_dataSize = dataSize;
}

unsigned char* DIB::data()
{
	return _data;
}

unsigned int DIB::dataSize() const
{
	return _dataSize;
}

void DIB::setFormat( Format format )
{
	_format = format;
}

DIB::Format DIB::format()
{
	return _format;
}

bool DIB::convertTo( DIB &dib, Format format ) const
{
	// dib is an output parameter.

	SIZE srcSize = size();
	SIZE dstSize = size( &format->bmiHeader );

	// Create a device dependent bitmap (DDB).
	HDC hDCScreen = GetDC( NULL );
	HBITMAP hBitmap = CreateCompatibleBitmap( hDCScreen, srcSize.cx, srcSize.cy );
	ReleaseDC( NULL, hDCScreen );

	// Create a memory DC holding the new DDB.
	HDC hDC = CreateCompatibleDC( NULL );
	SelectObject( hDC, hBitmap );

	// Set the mode for scaling images.
	SetStretchBltMode( hDC, HALFTONE );

	if( StretchDIBits(
		hDC, //hDC
		0, //xDest
		0, //yDest
		dstSize.cx, //DestWidth
		dstSize.cy, //DestHeight
		0, //xSrc
		0, //ySrc
		srcSize.cx, //SrcWidth
		srcSize.cy, //SrcHeight
		_data, //lpBits
		&*_format, //lpbmi
		DIB_RGB_COLORS, //iUsage
		SRCCOPY //rop
		) == 0 )
	{
		DeleteDC( hDC );
		DeleteObject( hBitmap );
		return false;
	}

	// Allocate the raw output buffer. Each scanline is DWORD aligned.
	unsigned int bytesPerScanLine = dstSize.cx * ( format->bmiHeader.biBitCount >> 3 );
	bytesPerScanLine += ( 4 - ( bytesPerScanLine % 4 ) ) % 4;
	unsigned int dataSize = srcSize.cy * bytesPerScanLine;
	unsigned char *dataOut = new unsigned char[ dataSize ];

	if( bytesPerScanLine == 0 )
	{
		DeleteDC( hDC );
		DeleteObject( hBitmap );
		delete[] dataOut;
		return false;
	}

	dib.setFormat( format );

	// GetDIBits cannot handle images larger than 64K. We thus split the image
	// in horizontal stripes, each one having a size of 64K max. The number of
	// scan lines in each stripe is scanLinesPerCycle.
	unsigned int scanLinesPerCycle = 65536 / bytesPerScanLine;
	if( scanLinesPerCycle == 0 )
	{
		// Each row in the image is larger than 64K (> 21845 pixels wide).
		DeleteDC( hDC );
		DeleteObject( hBitmap );
		delete[] dataOut;
		return false;
	}
	for( unsigned int i = 0; i < (unsigned int) srcSize.cy; i += scanLinesPerCycle )
	{
		//Copy bitmap data from the DDB. The GDI software emulation does not support
		//images larger than 64K, the image may thus be copied in several cycles.
		unsigned int toCopy =
			( scanLinesPerCycle > srcSize.cy - i ) ?
				srcSize.cy - i:
				scanLinesPerCycle;
		if( GetDIBits(
			hDC, // Handle to the device context.
			hBitmap, // Handle to the dependent bitmap.
			i, // First scanline to copy.
			toCopy, // Number of scanlines to copy.
			dataOut + ( i * bytesPerScanLine ), // Pointer to raw output buffer.
			&*dib._format, // Destination format (bitmap info).
			DIB_RGB_COLORS // Destination format (RGB or palette).
			) == 0 )
		{
			DeleteDC( hDC );
			DeleteObject( hBitmap );
			delete[] dataOut;
			return false;
		}
	}

	// Free resources.
	DeleteDC( hDC );
	DeleteObject( hBitmap );
	
	dib.setData( dataOut, dataSize );
	
	return true;
}

SIZE DIB::size() const
{
	return size( &(_format->bmiHeader) );
}

SIZE DIB::size( const BITMAPINFOHEADER *header )
{
	SIZE ret;

	// There are multiple header types available for a Windows bitmap.
	// Switch between all recognized header types.
	switch( header->biSize )
	{
	case sizeof( BITMAPINFOHEADER ):
		ret.cx = header->biWidth;
		ret.cy = header->biHeight;
		break;
	case sizeof( BITMAPV4HEADER ):
		ret.cx = ( (LPBITMAPV4HEADER) header )->bV4Width;
		ret.cy = ( (LPBITMAPV4HEADER) header )->bV4Height;
		break;
	case sizeof( BITMAPV5HEADER ):
		ret.cx = ( (LPBITMAPV5HEADER) header )->bV5Width;
		ret.cy = ( (LPBITMAPV5HEADER) header )->bV5Height;
		break;
	case sizeof( BITMAPCOREHEADER ):
		ret.cx = ( (LPBITMAPCOREHEADER) header )->bcWidth;
		ret.cy = ( (LPBITMAPCOREHEADER) header )->bcHeight;
		break;
	default:
		ret.cx = 0;
		ret.cy = 0;
		break; // Unrecognized header format.
	}

	return ret;
}

WORD DIB::bitCount() const
{
	// Switch between different header types.
	const BITMAPINFOHEADER *header = &_format->bmiHeader;
	switch( header->biSize )
	{
	case sizeof( BITMAPINFOHEADER ):
		return header->biBitCount;
	case sizeof( BITMAPV4HEADER ):
		return ( (LPBITMAPV4HEADER) header )->bV4BitCount;
	case sizeof( BITMAPV5HEADER ):
		return ( (LPBITMAPV5HEADER) header )->bV5BitCount;
	case sizeof( BITMAPCOREHEADER ):
		return ( (LPBITMAPCOREHEADER) header )->bcBitCount;
	default:
		return 0; // Unrecognized header format.
	}
}

bool DIB::bottomUp() const
{
	return ( size().cy > 0 );
}

bool DIB::topDown() const
{
	// For a top down dib, the height is stored as a negative value.
	return !bottomUp();
}
